// Version: 0.3
// ANTLR Version: 3.1.1
// Date: 2008.12.19
//
// Description: based on DB2 SELECT statement grammar.
//
// =======================================================================================
// Author: wong.tong@gmail.com

grammar QTemplate;

options {
    k = 2;
    output = AST;
}

// LEXER *********************************************************************************
tokens {
	AS = 'as';
	ORDER = 'order';
	BY = 'by';
	ASC = 'asc';
	DESC = 'desc';
	FETCH = 'fetch';
	FIRST = 'first';
	FROM = 'from';
	ROW = 'row';
	ROWS = 'rows';
	ONLY = 'only';
	FOR = 'for';
	UPDATE = 'update';
	OF = 'of';
	READ = 'read';
	OPTIMIZE = 'optimize';
	ALL = 'all';
	WITH = 'with';
	NC = 'nc';
	UR = 'ur';
	CS = 'cs';
	KEEP = 'keep';
	LOCKS = 'locks';
	RS = 'rs';
	RR = 'rr';
	SELECT = 'select';
	UNION = 'union';
	DISTINCT = 'distinct';
	TABLE = 'table';
	WHERE = 'where';
	GROUP = 'group';
	HAVING = 'having';
	CROSS = 'cross';
	JOIN = 'join';
	INNER = 'inner';
	LEFT = 'left';
	RIGHT = 'right';
	OUTER = 'outer';
	EXCEPTION = 'exception';
	ON = 'on';
	NOT = 'not';
	AND = 'and';
	OR = 'or';
	SOME = 'some';
	ANY = 'any';
	BETWEEN = 'between';
	EXISTS = 'exists';
	IN = 'in';
	LIKE = 'like';
	ESCAPE = 'escape';
	IS = 'is';
	NULL = 'null';
	CONCAT = 'concat';
/*
	CURRENT = 'current';
	DATE = 'date';
	PATH = 'path';
	FUNCTION = 'function';
	SCHEMA = 'schema';
	SERVER = 'server';
	TIME = 'time';
	TIMESTAMP = 'timestamp';
	TIMEZONE = 'timezone';
	USER = 'user';
*/
	CASE = 'case';
	WHEN = 'when';
	THEN = 'then';
	ELSE = 'else';
	END = 'end';
	CAST = 'cast';

/*TOKENs for Nodes of Tree*/
	StartNode;
	QueryNode;
	SubQueryNode;
	NameNode;
	AliasNode;
	OrdersNode;
	OrderNode;
	SelectNode;
	ColumnsNode;
	ColumnNode;
	TablesNode;
	TableNode;
	TableFunctionNode;
	PaginationNode;
	GroupByNode; 
	HavingNode;
	ConditionsNode;
	ExpressionsNode;
	ExpressionNode;
	CrossJoinNode;
	InnerJoinNode;
	ExceptionJoinNode;
	LeftExceptionJoinNode;
	RightExceptionJoinNode;
	JoinNode;
	LeftJoinNode;
	RightJoinNode;
	ExistsNode;
	FunctionNode;
	ArrayNode;
	CompareNode;
	NotNullNode;
	CastNode;
	CaseExpressionNode;
	CaseNode;
}

@header {
package astudy.parser.sql;
}
@lexer::header {
package astudy.parser.sql;
}

//symbol
COMMA				: ',' ;
DOUBLE_STAR 		: '**';	
STAR				: '*';
SEMICOLON			: ';' ;
LPAREN				: '(' ;
RPAREN				: ')' ;
EQ					: '=';
NE					: '<>'|'!=';
LE					: '<';
GT					: '>';
NL					: '>='|'!<';
NG					: '<='|'!>';
PLUS				: '+';
MINUS				: '-';
DOUBLE_BITWISEOR	: '||';
DIVIDE				: '/';
//UNDERLINE			: '_';

//numbers
fragment DIGIT		: '0'..'9' ;
INTEGER				: (DIGIT)*;	//do not care the sign: PLUS and MINUS. In parser, we will judge it.
DECIMAL				: INTEGER('.'(DIGIT)*)?;
FLOAT_POINT			: DECIMAL('E'(PLUS|MINUS)?INTEGER);
//DECIMAL_POINT		: 'ASDFGHJKL';

//strings
CHARACTER_STRING	: '\'' (~'\'')* '\'' ( '\'' (~'\'')* '\'' )*;
TABLE_COLUMNS		: ':'?('a'..'z'|'A'..'Z'|'#'|'@')('a'..'z'|'A'..'Z'|'0'..'9'|'#'|'@'|'_'|'.')*'.*';
IDENTIFIER			: ':'?('a'..'z'|'A'..'Z'|'#'|'@')('a'..'z'|'A'..'Z'|'0'..'9'|'#'|'@'|'_'|'.')*;

WHITESPACE 			: ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+ 	{ $channel = HIDDEN; } ;
SINGLELINE_COMMENT	: '--'( ~('\r' | '\n') )*		{ $channel = HIDDEN; } ;
MULTILINE_COMMENT	: '/*' (~'*')* '*' ('*' | ( ~('*' | '/') (~'*')* '*') )* '/'  	{ $channel = HIDDEN; } ;

// PARSER *********************************************************************************
// starting rule
start
	: 	ss+=selectStatement (SEMICOLON ss+=selectStatement)* (SEMICOLON)?
		EOF
		-> ^(StartNode $ss*)
	;

selectStatement	//page 349
	:	
    	(WITH commonTableExpression (COMMA commonTableExpression)* )? 
	   	fullselect
    	(orderByClause)?
	   	(fetchFirstClause)?
    	(updateClause | readOnlyClause | optimizeClause | isolationClause)*
    	
    	-> ^(QueryNode 
    			commonTableExpression* 
				fullselect orderByClause? fetchFirstClause?
				updateClause* readOnlyClause* optimizeClause* isolationClause*
    		)
    ;

commonTableExpression	//page 350
    :	
    	(STAR | refid=identifier) 	 //can be table/view name
    	(LPAREN columnName (COMMA columnName)* RPAREN)?
    	AS LPAREN 
    		fullselect 
    		(orderByClause)? 
    		(fetchFirstClause)? 
    	RPAREN
    	
    	-> ^(SubQueryNode 
    			^(NameNode $refid) 
    			fullselect 
    			orderByClause? 
    			fetchFirstClause?
    		)
	;

/**
 * tableName can be :
 * - column name
 * - table name
 * - view name
 * - correlation name
 * - function name
 * we can judge it from its context.
 */
identifier	
	:	IDENTIFIER
	;
	
columnName
	:	IDENTIFIER
	;
	
fullselect	//page 346
	:	subQueryExpression
		( UNION (DISTINCT | ALL)? subQueryExpression )*
		
		-> subQueryExpression+
	;
	
subQueryExpression
	:	subselect
		-> subselect
		
		| LPAREN fullselect RPAREN
		-> fullselect
	;

subselect	//page 330
	:	selectClause 
		fromClause 
		whereClause? 
		groupByClause? 
		havingClause?
		
		-> ^(SelectNode 
				fromClause		//tables
				selectClause 	//columns
				whereClause? 	//conditions
				groupByClause? 
				havingClause?
			)
	;
	
selectClause	//page 331
	:	SELECT ( ALL | DISTINCT )?
		(selectItem (COMMA selectItem)*)
		
		-> ^(ColumnsNode ^(ColumnNode selectItem)+) 
	;
	
selectItem
	:	STAR	// "*, *" is a valid select list?
		-> ^(NameNode STAR)
		| TABLE_COLUMNS 	//genericName can be table/view/correlation name
		-> ^(NameNode TABLE_COLUMNS)
		| expression (AS? columnName)?
		-> ^(NameNode expression) ^(AliasNode columnName)?
	;

expression	//page 129
	:	 subExpression  (binaryOperator subExpression)*
	;
	
subExpression
	:
		unaryOperator?
		(
			function
		 	| LPAREN (scalarSubSelect | expression) RPAREN
		 	| constant
		 	| columnName
//		 	| hostVariable
		 	| specialRegister
//		 	| labeledDuration
		 	| castSpecification
		 	| caseExpression
		 )
		 -> unaryOperator? 
		 	function? 
		 	scalarSubSelect? 
		 	expression? 
		 	constant?
		 	columnName?
//		 	hostVariable?
			specialRegister?
//			labeledDuration?
			castSpecification?
			caseExpression?
	;
	
function
	:	functionName=identifier //function name 
		LPAREN 
			( STAR | (ALL|DISTINCT)? (expression (COMMA expression)*) )? 
		RPAREN
		-> ^(FunctionNode ^(NameNode $functionName) ^(ArrayNode expression*)?)
	;

constant	//page 99
	:	(PLUS|MINUS)?INTEGER |
		DECIMAL |
		FLOAT_POINT |
		CHARACTER_STRING |
		'GRAPHIC_STRING' |
		'BINARY-STRING' |
		'DECIMAL_POINT' |
		'Delimiters'
	;
/*
//not supported
hostVariable
	:	'hostVariable'
	;
*/
specialRegister
	:	'current date'
/*
		CURRENT (
			UNDERLINE? (
				 DATE | PATH | SERVER | TIME | TIMESTAMP | TIMEZONE
			) |
			FUNCTION PATH |
			SCHEMA
		) |
		USER
*/
	;

scalarSubSelect
	:	subselect
	;

labeledDuration
	:	(
			function | 
			LPAREN expression RPAREN |
			constant |
			columnName|
//			hostVariable
		)  
		IDENTIFIER //here, IDENTIFIER is YEAR|YEARS|MONTH|MONTHS|DAY|DAYS|HOUR|HOURS|MINUTE|MINUTES|SECOND|SECONDS|MICROSECOND|MICROSECONDS
	;

castSpecification
	:	CAST LPAREN 
			exp=expression AS 
			type=identifier (LPAREN i+=INTEGER (COMMA i+=INTEGER)? RPAREN)? 
		RPAREN
		-> ^(CastNode $exp $type $i+)
	;
	
caseExpression
	:	CASE  (
			exp=expression (WHEN i+=expression THEN i+=expression)+
			| (WHEN i+=searchCondition THEN i+=expression)+    // boolean expression
		)
		(ELSE j=expression)? 
		END
		-> {exp == null}? ^(CaseNode $i* $j?)
		-> ^(CaseExpressionNode ^(ExpressionNode $exp) $i* $j?) 
	;
	
compareOperator
	: 	(EQ|NE|LE|GT|NL|NG)
	;
	
unaryOperator
	: 	PLUS | MINUS
	;

binaryOperator
	: 	arithmeticOperator | bitwiseOperator
    ;

arithmeticOperator
	:	CONCAT |
		DIVIDE |
		STAR |
		DOUBLE_STAR |
		PLUS |
		MINUS
	;
		
bitwiseOperator
	:	DOUBLE_BITWISEOR
	;

fromClause
	:	FROM tableReference (COMMA tableReference)*
		-> ^(TablesNode tableReference+)
	;

singleTable	//page 335
	:	refid=identifier/*table/view name*/ (refcol=correlationClause)?
		-> ^(TableNode ^(NameNode $refid) ^(AliasNode $refcol)?)
	;
	
nestedTableExpression	//page 335
	:	LPAREN 
			fullselect orderByClause? fetchFirstClause? 
		RPAREN 
		refid=correlationClause
		-> ^(SubQueryNode ^(NameNode $refid) fullselect orderByClause? fetchFirstClause?)
	;

tableFunction
	:	TABLE LPAREN 
			name=identifier //function name 
			LPAREN (params+=expression (COMMA params+=expression)*)? RPAREN 
		RPAREN ref_table=correlationClause
		-> ^(TableNode ^(TableFunctionNode $name $params*) ^(AliasNode $ref_table))
	;

tableReference	//page 335
	:	(
			(
				joinedTable
				| singleTable
				| nestedTableExpression
				| tableFunction
			)  tableReference
		)?

	;
	
joinedTable :	//page 338
		CROSS JOIN tableReference
		-> ^(CrossJoinNode tableReference) 
		| (
			inner=INNER
			| (dir=LEFT|dir=RIGHT) OUTER?
			| (dir=LEFT|dir=RIGHT)? exception=EXCEPTION
		)? JOIN tableReference ON searchCondition
		
		-> 	{inner != null}?
			^(InnerJoinNode tableReference ^(ConditionsNode searchCondition))
		/* exception */
		-> 	{exception != null && dir == null}?  
			^(RightExceptionJoinNode tableReference ^(ConditionsNode searchCondition))
		-> 	{exception != null && "left".equalsIgnoreCase(dir.getText())}?  
			^(LeftExceptionJoinNode tableReference ^(ConditionsNode searchCondition))
		-> 	{exception != null && "right".equalsIgnoreCase(dir.getText())}?  
			^(RightExceptionJoinNode tableReference ^(ConditionsNode searchCondition))
		/* outer */
		->  {dir == null}?
			^(JoinNode tableReference ^(ConditionsNode searchCondition))
		->  {"left".equalsIgnoreCase(dir.getText())}?
			^(LeftJoinNode tableReference ^(ConditionsNode searchCondition))
		->  {"right".equalsIgnoreCase(dir.getText())}?
			^(RightJoinNode tableReference ^(ConditionsNode searchCondition))
		/* default */
		->  /*nothing here*/
	;

correlationClause	//page 335
	:	AS? refid=identifier //correlation name
		( LPAREN columnName (COMMA columnName)* RPAREN )?
		->  $refid
	;

whereClause	//page 340
	:	WHERE searchCondition
		-> ^(ConditionsNode searchCondition)
	;

searchCondition	//page 161
	:	subSearchCondition ((op=AND|op=OR) subSearchCondition)*
		-> ^(ExpressionNode subSearchCondition) ($op ^(ExpressionNode subSearchCondition))*
	;
	
subSearchCondition
	:	not=NOT? (
			(LPAREN searchCondition RPAREN) => LPAREN searchCondition RPAREN
			| predicate
		)
		-> $not? searchCondition? predicate?
	;
/**
 * predicate
 * 
 */
predicate	//page 147
	:	EXISTS LPAREN subselect RPAREN 			//exits predicate
		-> ^(ExistsNode subselect)
		
		| exp=expression  (
			comp=compareOperator  (
				exp1=expression		//basic predicate
				-> ^(CompareNode $comp $exp $exp1)
				| (num=SOME|num=ALL|num=ANY) LPAREN subselect RPAREN	//quantified predicate
				-> ^(CompareNode $comp $exp $num subselect)
			)
			| NOT? (
				LIKE exp1=expression (ESCAPE expression)?	//like predicate
				-> ^(LIKE $exp $exp1)
				| BETWEEN exp1=expression AND exp2=expression 	//between predicate
				-> ^(BETWEEN $exp $exp1 $exp2)
				| IN  LPAREN (
					subselect | i+=expression (COMMA i+=expression)* 
				) RPAREN		//in predicate
				-> {$i == null}? ^(IN $exp subselect)
				->               ^(IN $exp ^(ArrayNode $i*))
			)
			| IS NOT? NULL 		 //null predicate
			-> ^(NotNullNode $exp)
		)
	;
	
groupByClause	//page 341
	:	GROUP BY groupingExpression
		-> ^(GroupByNode groupingExpression)
	;
	
groupingExpression
	:	expression (COMMA expression)*
		-> ^(ExpressionsNode ^(ExpressionNode expression)*)
	;
	
havingClause
	:	HAVING searchCondition
		-> ^(HavingNode searchCondition)
	;

orderByClause	//page 351
	:	ORDER BY subOrder (COMMA subOrder)*
		-> ^(OrdersNode subOrder*)
	;

subOrder
	:	sortKey (order=ASC|order=DESC)?
		-> ^(OrderNode sortKey $order?)
	;	
	
sortKey
	:	expression
	;

	
fetchFirstClause
	:	FETCH FIRST (rows=INTEGER)  (ROW | ROWS) ONLY
		-> ^(PaginationNode $rows)
	;

updateClause	//page 	354
	:	FOR UPDATE (OF columnName (COMMA columnName)* )?
	;
	
readOnlyClause	//page 355
	:	FOR READ ONLY
	;
	
optimizeClause	//page 356
	:	OPTIMIZE FOR (INTEGER | ALL) (ROW | ROWS)
	;
	
isolationClause	//page 357
	:	WITH (NC | UR | CS (KEEP LOCKS)? | RS | RR)
	;